/**
 * Async :	An asynchronous data and yield management class.
 * 
 * @langversion		HaXe 2
 * @targets			cpp, flash, flash9, js, neko, php
 * @author			Justin Donaldson | jdonaldson@gmail.com
 * @since			2010-08-20
 */
class Async<T> {
	
	private static var hfuncs = new IntHash<Array<Void->Void>>();
	private static var reference_ids = 0;

	private var id:Int;
	public var val(default, null):T;
	public var set(default, null):Bool;
	
	public function new(){

		this.id = reference_ids++;
		hfuncs.set(this.id,[]);
		this.set = false;
	}
	
	public function yield(val:T) {
		this.val = val;
		set = true;
		Lambda.foreach(hfuncs.get(id), function(x) {x(); return true;});
/*		clearListeners();*/
	}
	
	public function clearListeners(){
		if (hfuncs.exists(id)) hfuncs.set(id,[]);
	}
	
	public function reset(){
		this.clearListeners();
		this.val = null;
		this.set = false;
	}
	
	public function removeListener(f:Dynamic){
		if (hfuncs.exists(id)) {
			for (i in hfuncs.get(id)){
				if (Reflect.compareMethods(f,i)) return true;
			}
		}
		return false;
	}
	
	
	private static function _wait(f:Dynamic, args:Array<Dynamic>):Async<Dynamic>{
		var av = new Async<Dynamic>();	
		var f_proxy = function(){
			if (!isWaitingOnArgs(args))	{ 
				var av2  = Reflect.callMethod({},f,args);
				if (av2 == null) return; // void return
				else if (Std.is(av2,Async)){
					var av2:Async<Dynamic> = cast av2;
					if (av2.set) av.yield(av2.val); // yield was triggered on the return already, propogate to the function listeners.
					else{
						hfuncs.set(av.id, hfuncs.get(av.id).concat(hfuncs.get(av2.id))); // move all the listeners from the current var to the function var
						av2.clearListeners(); // and clear the old one...
					}	
				} 
				
						
			}
		}		
		setListenersForArgs(args, f_proxy);
		f_proxy();
		return av;
	}
	
	private static function isWaitingOnArgs(args:Array<Dynamic>) : Bool{
		for (a in args){
			if (Std.is(a, Async)){
				var a:Async<Dynamic> = cast(a);
				if (a.val == null) return true;
			}
		}
		return false;
	}
	
	private static function setListenersForArgs(args:Array<Dynamic>, f_callback:Dynamic){
		for (a in args){
			if (Std.is(a, Async)){
				var a:Async<Dynamic> = cast(a);
				hfuncs.get(a.id).push(f_callback);
			}
		}
	}

	public function toString(){
		return Std.string(val);
	}
	
	public static function wait1<A,B>(f:A->B,arg1:A):B{
		
		return untyped _wait(f,[arg1]);
	}
	
	public static function wait2<A,B,C>(f:A->B->C,arg1:A,arg2:B):C{
		return untyped _wait(f,[arg1, arg2]);
	}
	
	public static function wait3<A,B,C,D>(f:A->B->C->D,arg1:A,arg2:B,arg3:C):D{
		return untyped _wait(f,[arg1, arg2, arg3]);
	}
	
}

